Recap of Part 1

In Part 1 of this workshop, we attempted to predict the value of a single, continuous outcome variable hourly_wage based on a range of input features, including age, location, and industry. Specifically, we covered:

Today, we will apply and expound upon these principles to develop models to predict categorical variables. This process is called ‘classification.’

Install Packages and Load Data

Before we dive into today’s material, let’s load our libraries tidymodels and tidyverse libraries.

Other Tasks in Supervised Machine Learning: Classification

Thus far, we’ve spent most of our time working through regression problems (i.e., predicting a continuous outcome variable). Let’s switch to a new task: classification. In classification, we aim to predict one of a group of values. For example, predicting a qualitative response is considered classification because we assign the observation to a category (or class).

Like regression, classification is also a supervised learning technique because we have a set of labeled training data that we can use to build a classifier. Now, however, we have access to different techniques because the structure of our outcome variable is categorical.

📝 Poll 1: Classification vs. Regression

Identify which of the following are classification problems in machine learning.

  1. An advertiser is interested in the relationship between age and the number of hours of YouTube consumed.

  2. A medical testing company conducts a procedure to determine whether a person has a cancer diagnosis.

  3. A researcher is interested in the effect of an education intervention on students’ test scores.

  4. A software engineer is designing an algorithm to detect whether an email is spam or not.

  5. A political scientist wants to classify Twitter posts as positive or negative.

Today’s Data: Voting Patterns in the 2020 Election

Now, let’s load our primary data set for today’s workshop: vote2020. Our goal is going to be predicting whether someone voted in the 2020 election, perhaps to tailor engagement strategies toward those least likely to vote in an upcoming election. These data are based on the Current Population Survey’s Voter Supplement.

vote2020 <- read.csv("../data/vote2020.csv",row.names = NULL)

# visually inspect the data frame 
view(vote2020)

Exploratory Analysis

Recall that the first step in machine learning (and most analyses) is to explore the data we’re working with to get a sense of its shape and anticipate problems that may arise.

🥊 Challenge 1: Exploratory Data Analysis

Perform exploratory analyses on the vote2020 data set, keeping in mind that today’s goal is to predict voted. What do you notice about the data?


 ## YOUR CODE HERE 

Train-Test Split

Recall from Part 1 that a critical first step in machine learning is partitioning our data into training and test sets.

🔔 Question 1: Train-Test Split

A researcher used the entire vote2020 data set to train a model that resulted in impressively high accuracy during evaluation. However, upon deploying this model to predict voter turnout for an upcoming election, the predictions significantly diverged from actual turnout, with many discrepancies in who was predicted to vote versus who actually voted. The model’s near-perfect performance in development starkly contrasted its poor real-world prediction outcomes, indicating a potential oversight in the model training and evaluation process. Why might that be?

Answer 1:

Now that we have a better sense of the data, let’s go ahead and split vote2020 into training and test sets:

# Perform splits
vote2020$voted <- as.factor(vote2020$voted)
vote_split <- initial_split(vote2020, prop = 0.75)
vote_train <- training(vote_split)
vote_test <- testing(vote_split)

Pre-Processing

In our example from Part 1, we prepared our data for analysis by recoding categorical variables and normalizing numeric ones using step_dummy(all_nominal_predictors()) and step_normalize(), respectively.

In that example, we also dropped rows that had any missing values across variables. Let’s try another example, in which we don’t omit samples that have missing values, but instead perform imputation, in which we replace those missing values according to certain criteria. There are various kinds of imputation, including:

There are other ways to impute, but these are good starting points. The way to perform imputation in a recipe is via the step_impute_ functions.

🥊 Challenge 2: Creating a Recipe

Using the same logic from Part 1, create your own recipe called voterecipe that lays out the steps for pre-processing the data. Instead of dropping rows with missing values, use step_impute_median to impute numeric variables and step_impute_mode to impute categorical variables.

voterecipe <- 
  recipe(_____ ~ ., data = _____) %>%
  step_impute_median(_____) %>%
  step_impute_mode(_____) %>%
  step_dummy(_____) 

This pre-processing will allow us to take advantage of samples with missing data, even if it comes at a little cost to accuracy. Imputation is often a necessary step, since it’s common to have missing data.

🔔 Question 2: Imputation

Notice that we have only applied our recipe to the training data so far. Explain why it is important to perform imputation separately on the training and test sets rather than imputing missing values before splitting the data set. Consider why we don’t want the data we train our model on to be exposed to our test data set in the first place.

Answer 2:

Developing and Evaluating Models

Now that we’ve performed exploratory analyses, a train-test split, and pre-processing, let’s go ahead and train our model. We will create a classifier (i.e., a model that predicts membership in a group) with logistic regression.

Algorithm 1: Logistic Regression

Machine learning practitioners often recommend logistic regression as a starting model when predicting a binary outcome or probability. For example, if we are estimating the relationship between mortality and income, we can estimate the probability of mortality given a change in income.

We write this as \(P[M|I]\) and the values will range between 0 and 1. We can make a prediction for any given value of income on the mortality outcome. Normally, we establish a threshold for prediction. For example, we might predict death (M) where \(P[M|i] > 0.5\).

Logistic regression is a generalized linear model where we model the probability function using the logistic function, an s-shaped curve sometimes called a sigmoid function. The general function is: \(p(Y= 1|X)\). In the example below, the green data points will be classified as 0 because \(p\) < 0.5; the orange points will be classified as 1 because \(p\) > 0.5.

Logistic Regression
Logistic Regression

Here’s how to fit classification problems in tidymodels using logistic regression.

In tidymodels, creating a logistic regression follows the exact same procedure as a linear regression. This time, however, we will use logistic_reg() to initiate the function. Let’s create the model:

# Create model
logistic_model <- logistic_reg(mode = "classification")

Now, let’s fit the model on the training data. We first create a workflow that lists instructions for pre-processing and the model we want to use, and then apply it to the training data.

vote_wflow <- workflow() %>%
  add_recipe(voterecipe) %>%
  add_model(logistic_model)

vote_fit <- fit(vote_wflow, vote_train)
vote_fit %>% tidy()

Finally, let’s use augment from the yardstick package to obtain the predictions, and take a look at them:

logistic_predictions <- augment(vote_fit, new_data = vote_test)
logistic_predictions[,14:17] # look at last 4 columns 

🔔 Question 3: Analyzing Output

Notice that four new columns have appeared in our data set. What do these values mean? What are they telling us?

Answer 3:

To evaluate the model, we’ll use the accuracy function:

accuracy(logistic_predictions, truth = voted, estimate = .pred_class, type="class")

We predicted the likelihood that someone voted in the last election with an accuracy of about 81% - not bad!

We can also create what’s called a ‘confusion matrix’ to see how well our model did at predicting each class. A confusion matrix is helpful if you care about false positives (predicting 1 when the true value is 0) and false negatives (predicting 0 when the true value is 1). In the context of testing for Covid-19, we might want care more about failing to diagnose a person with the virus (i.e., a false negative) because the stakes are higher.

logistic_predictions %>%
  conf_mat(truth = voted, estimate = .pred_class)

So far we have trained a simple logistic regression model, there are many other options in our machine learning arsenal to get a better prediction.

Hyperparameter Tuning and Regularization

Notice that until now, we have used the default settings for the linear and logistic regressions we have run. Looking at the (documentation)[https://www.rdocumentation.org/packages/parsnip/versions/0.0.0.9001/topics/logistic_reg] for logistic regression, we see that there are many arguments that we left blank when we initialized our model above. These arguments, also known as ‘parameters’, correspond to statistical choices about how the model should operate or be structured.

Some of these hyperparameters include ‘engine’ and ‘penalty’.

  • Engine: Different engines can implement regression through various algorithms or computational approaches. When you specify an engine for logistic regression in R, you’re choosing the particular set of algorithms and optimizations that will be used to train your model.

  • Penalty: “penalty” refers to a regularization technique used to prevent overfitting by discouraging overly complex models. It does this by adding a penalty to the loss function for large coefficients. Common penalties are L1 (Lasso), which can shrink some coefficients to zero (thus performing feature selection), and L2 (Ridge), which shrinks all coefficients toward zero but typically doesn’t set any to exactly zero. The penalty helps in creating simpler, more generalizable models that perform better on unseen data by prioritizing the most influential features and reducing the model’s sensitivity to the training data’s noise.

For example, we can add a ‘penalty’ on the size of the coefficients of a model and reduce the likelihood of overfitting. Lasso, ridge, and elastic net are different types of penalties that greatly reduce or shrink to zero coefficients on variables that are picking up on a lot of noise. The broad technique of reducing overfitting is called ‘regularization.’

🥊 Challenge 3: Changing Hyperparameters

We have reproduced the original code from above that trained a basic classifier on our voting data set without changing any of the hyperparameters, as well as the code that obtains predictions. Re-run this code several times, but each time change the hyperparameters in the model specification. For penalty, include a non-negative number; and for engine, select either “glmnet” or “glm”. How does the accuracy change?

chal3_model <- logistic_reg(mode = "classification", 
                               engine="glmnet", 
                               penalty=____)

chal3_wflow <- workflow() %>%
  add_recipe(____) %>%
  add_model(____)

chal3_fit <- fit(chal3_wflow, ____)

chal3_predictions <- augment(____, new_data = ____)

accuracy(chal3_predictions, truth = ____, estimate = .pred_class, type="class")

It’s nice that we can do different types of regularization, but how do we know what value of the penalty coefficient to pick? In machine learning, this value - which we choose before fitting the model - is known as a hyperparameter. Since hyperparameters are chosen before we fit the model, we can’t just choose them based off the training data. So, how should we go about conducting hyperparameter tuning: identifying the best hyperparameter(s) to use?

Let’s think back to our original goal. We want a model that generalizes to unseen data. So, ideally, the choice of the hyperparameter should be such that the performance on unseen data is the best. We can’t use the test set for this, but what if we had another set of held-out data?

Hyperparameter tuning

Cue hyperparameter tuning! Hyperparameter tuning is crucial in machine learning because it directly impacts the performance and effectiveness of models. By fine-tuning hyperparameters, practitioners can optimize models to achieve higher accuracy, better generalize to unseen data, and prevent issues like overfitting or underfitting. This process allows for the customization of models to specific data sets and objectives, enabling the discovery of the best configuration for a given problem.

Choosing Hyperparameters: Validation Sets

This is the basis for a validation set. If we had extra held-out data set, we could try a bunch of hyperparameters on the training set, and see which one results in a model that performs the best on the validation set. We then would choose that hyperparameter, and use it to refit the model on both the training data and validation data. We could then, finally, evaluate on the test set.

Validation set
Validation set

Cross Validation

We just formulated the process of choosing a hyperparameter with a single validation set. However, there are many ways to perform validation. The most common way is cross-validation. Cross-validation is motivated by the concern that we may not choose the best hyperparameter if we’re only validating on a small fraction of the data. If the validation sample, just by chance, contains specific data samples, we may bias our model in favor of those samples, and limit its generalizability.

So, during cross-validation, we effectively validate on the entire training set, by breaking it up into folds. Here’s the process: We can use a process called cross-validation to do select the best

  1. Perform a train-test split, as you normally would.
  2. Choose a number of folds - the most common is \(K=5\) - and split up your training data into those equally sized “folds”.
  3. For each hyperparameter you want to tune, specify the possible values it could take. Then, for each hyperparameter:
    1. For each value of that hyperparameter, we’re going to fit \(K\) models. Let’s assume \(K=5\). The first model will be fit on Folds 2-5, and validated on Fold 1. The second model will be fit on Folds 1, 3-5, and validated on Fold 2. This process continues for all 5 splits.
    2. The performance of each value of that hyperparameter is summarized by the average predictive performance on all 5 held-out folds. We then choose the hyperparameter value that had the best average performance.
  4. We can then refit a new model to the entire training set, using our chosen hyperparameter(s). That’s our final model - evaluate it on the test set!
cross-validation
cross-validation

Hyperparameter Tuning and Cross Validation in Practice

We need to do two things:

  1. Decide to perform hyperparameter tuning on the penalty value, and
  2. Do so using cross-validation.

The tidymodels suite has two packages to help us with these steps: tune and rsample.

Let’s illustrate both these packages in the classification example. We already have a recipe set up:

voterecipe

── Recipe ───────────────────────────────────────────────────────────────────────────────────────────────────────────

── Inputs 
Number of variables by role
outcome:    1
predictor: 13

── Operations 
• Median imputation for: all_numeric_predictors()
• Mode imputation for: all_nominal_predictors()
• Dummy variables from: all_nominal_predictors()

When specifying the model, however, we’re going to do something slightly different:

tuned_logistic_model <- logistic_reg(
  penalty = tune(),
  engine = "glmnet")

We passed in a function called tune(). This signals to tidymodels that we’d like to tune this hyperparameter. How do we indicate what values we should test during tuning? We’re going to keep things simple and focus on the most basic choice of tuning: a grid search. In this case, we specify a range of values, and then test every single one for the hyperparameter. We use the grid_regular function for this procedure:

# Create grid of parameters
cv_grid <- grid_regular(
  penalty(range = c(-5, 5)), # select penalty values with -5 and 5 
  levels=10) # pick 10 values between -5 and 5 

print(cv_grid)

🔔 Question 4: Analyzing a Grid

We have a hyperparameter grid with 10 rows. What does each of these signify?

Solution 4:

🎬 Demo: Performing Hyperparameter Tuning with Cross Validation

Next, we need to specify how we will perform cross-validation. From the rsample package, we can use the function vfold_cv to create the training folds. In this case, v is what’s used for “K”.

vote_folds <- vfold_cv(vote_train, v = 5)
vote_folds
#  5-fold cross-validation 

We have a tuned model, a grid of hyperparameters, and a set of folds. We create our workflow as before: we input the recipe (i.e., the set of pre-processing instructions) and the model specification. But, to train the workflow, we use the tune_grid function. All the pieces we’ve created are passed into this function:

# Create workflow
vote_wflow <- workflow() %>%
  add_recipe(voterecipe) %>%
  add_model(tuned_logistic_model)

# Tune and fit models with tune_grid()
vote_cv_fit <- tune_grid(
  # The workflow
  vote_wflow, 
  # The folds we created
  resamples = vote_folds,
  # The grid of hyperparameters
  grid = cv_grid)

There are some nice plotting functions we can use to visualize how the performance varies as a function of the regularization. For example, check out the autoplot() function:

autoplot(vote_cv_fit, 
         metric="accuracy") 

Each of the ten points on this graph corresponds to a set of hyperparameters. We can see that the third combination yields the highest accuracy of about 81.5%. What does this tell us about how much regularization we should use?

Instead of picking the combination of hyperparamters that yields the best accuracy by eye, we can automate this procedure. The select_best function will do this for us:

# Select best metric according to accuracy 
vote_cv_best <- select_best(vote_cv_fit, metric = "accuracy")
vote_cv_best

Cross-validation selected a penalty value of 0.0017 as the best hyperparameter for our model. What do we do at this point?

Recall that, during cross-validation, we split up the data and examine performance across many folds. Now that we know what is likely the best penalty, we can re-train on the entire training set to produce our final model.

We do this with the finalize_workflow function.

# Get our final model and finalize workflow
cv_final <- vote_wflow %>%
  finalize_workflow(parameters = vote_cv_best) %>% # use the best parameters 
  fit(data = vote_train)
cv_final %>% tidy()

And lastly, we’ll examine the performance on the test set:

cv_predictions <- augment(cv_final, new_data = vote_test)

accuracy(cv_predictions, truth = voted, estimate = .pred_class, type="class")

🔔 Question 5: Accuracy on the Test Set

Notice our final accuracy on the test set is lower than our accuracy on the training set. Why might that be?

Solution 5:

Hyperparameter Tuning vs. Default Arguments

If we compare this to the original model we fit earlier where we made a guess at the best value for the penalty hyperparameter, it reveals that we that we can fit better models via cross-validation than with just a single training set. This is reflected in the higher accuracy compared with the model where we used the default arguments of logistic_reg().

Concluding Remarks

Congratulations, you’ve made it! We covered the basics of supervised machine learning in tidymodels in this workshop. However, there’s much more to explore. The best way to keep pushing forward is to choose a problem to study, and refer to the documentation when you need help. The website Kaggle has many good data science problems to work on if you need help choosing a task!

LS0tCnRpdGxlOiAiUiBNYWNoaW5lIExlYXJuaW5nIFBpbG90IChEYXkgMikiCm91dHB1dDogaHRtbF9ub3RlYm9vawplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCgojIFJlY2FwIG9mIFBhcnQgMQoKSW4gUGFydCAxIG9mIHRoaXMgd29ya3Nob3AsIHdlIGF0dGVtcHRlZCB0byBwcmVkaWN0IHRoZSB2YWx1ZSBvZiBhCnNpbmdsZSwgY29udGludW91cyBvdXRjb21lIHZhcmlhYmxlIGBob3VybHlfd2FnZWAgYmFzZWQgb24gYSByYW5nZSBvZgppbnB1dCBmZWF0dXJlcywgaW5jbHVkaW5nIGFnZSwgbG9jYXRpb24sIGFuZCBpbmR1c3RyeS4gU3BlY2lmaWNhbGx5LCB3ZQpjb3ZlcmVkOgoKLSAgIFRyYWluLXRlc3Qgc3BsaXRzOiBwYXJ0aXRpb25pbmcgdGhlIGRhdGEgc2V0IGludG8gYSB0cmFpbmluZyBzZXQKICAgIHVzZWQgdG8gdHJhaW4gb3VyIG1vZGVsLCBhbmQgYSB0ZXN0IHNldCB1c2VkIHRvIGV2YWx1YXRlIHRoZSBtb2RlbCdzCiAgICBwZXJmb3JtYW5jZS4KCi0gICBUaGUgYmlhcy12YXJpYW5jZSB0cmFkZS1vZmY6IHRoZSBiYWxhbmNlIGJldHdlZW4gdGhlIG1vZGVsJ3MgYWJpbGl0eQogICAgdG8gY2FwdHVyZSB0aGUgdHJ1ZSB1bmRlcmx5aW5nIHBhdHRlcm5zIG9mIHRoZSB0cmFpbmluZyBkYXRhIChiaWFzKQogICAgYW5kIGl0cyBmbGV4aWJpbGl0eSB0byBsZWFybiBmcm9tIHNwZWNpZmljIGluc3RhbmNlcyB3aXRoaW4gdGhhdAogICAgZGF0YSAodmFyaWFuY2UpLiBUaGlzIHRyYWRlLW9mZiBtYXR0ZXJzIGJlY2F1c2UgaXQgZGlyZWN0bHkgYWZmZWN0cwogICAgdGhlIGdlbmVyYWxpemF0aW9uIGFiaWxpdHkgb2YgdGhlIG1vZGVsIHRvIG5ldywgdW5zZWVuIGRhdGEuCgotICAgUHJlLXByb2Nlc3Npbmc6IGNsZWFuaW5nIGFuZCB0cmFuc2Zvcm1pbmcgcmF3IGRhdGEgaW50byBhIHN1aXRhYmxlCiAgICBmb3JtYXQgZm9yIGFuYWx5c2lzLCBlbnN1cmluZyB0aGUgZGF0YSBpcyBmcmVlIG9mIGVycm9ycywKICAgIGluY29uc2lzdGVuY2llcywgYW5kIGlycmVsZXZhbnQgaW5mb3JtYXRpb24uIEl0IG9mdGVuIGluY2x1ZGVzCiAgICBjb2RpbmcgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGFuZCBub3JtYWxpemluZyBudW1lcmljYWwgdmFsdWVzIHRvCiAgICByZWR1Y2UgYmlhcyBhbmQgaW1wcm92ZSBtb2RlbCBhY2N1cmFjeS4KClRvZGF5LCB3ZSB3aWxsIGFwcGx5IGFuZCBleHBvdW5kIHVwb24gdGhlc2UgcHJpbmNpcGxlcyB0byBkZXZlbG9wIG1vZGVscwp0byBwcmVkaWN0IGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gVGhpcyBwcm9jZXNzIGlzIGNhbGxlZAonY2xhc3NpZmljYXRpb24uJwoKIyBJbnN0YWxsIFBhY2thZ2VzIGFuZCBMb2FkIERhdGEKCkJlZm9yZSB3ZSBkaXZlIGludG8gdG9kYXkncyBtYXRlcmlhbCwgbGV0J3MgbG9hZCBvdXIgbGlicmFyaWVzCmB0aWR5bW9kZWxzYCBhbmQgYHRpZHl2ZXJzZWAgbGlicmFyaWVzLgoKYGBge3IgaW5zdGFsbCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSh0aWR5bW9kZWxzKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCiMgUHJlZmVyIHRpZHltb2RlbHMgZnVuY3Rpb25zIGluIGFueSBjYXNlIG9mIG5hbWUgY29uZmxpY3QKdGlkeW1vZGVsczo6dGlkeW1vZGVsc19wcmVmZXIoKSAKYGBgCgojIE90aGVyIFRhc2tzIGluIFN1cGVydmlzZWQgTWFjaGluZSBMZWFybmluZzogQ2xhc3NpZmljYXRpb24KClRodXMgZmFyLCB3ZSd2ZSBzcGVudCBtb3N0IG9mIG91ciB0aW1lIHdvcmtpbmcgdGhyb3VnaCByZWdyZXNzaW9uCnByb2JsZW1zIChpLmUuLCBwcmVkaWN0aW5nIGEgY29udGludW91cyBvdXRjb21lIHZhcmlhYmxlKS4gTGV0J3Mgc3dpdGNoCnRvIGEgbmV3IHRhc2s6IGNsYXNzaWZpY2F0aW9uLiBJbiBjbGFzc2lmaWNhdGlvbiwgd2UgYWltIHRvIHByZWRpY3Qgb25lCm9mIGEgZ3JvdXAgb2YgdmFsdWVzLiBGb3IgZXhhbXBsZSwgcHJlZGljdGluZyBhIHF1YWxpdGF0aXZlIHJlc3BvbnNlIGlzCmNvbnNpZGVyZWQgY2xhc3NpZmljYXRpb24gYmVjYXVzZSB3ZSBhc3NpZ24gdGhlIG9ic2VydmF0aW9uIHRvIGEKY2F0ZWdvcnkgKG9yIGNsYXNzKS4KCkxpa2UgcmVncmVzc2lvbiwgY2xhc3NpZmljYXRpb24gaXMgYWxzbyBhIHN1cGVydmlzZWQgbGVhcm5pbmcgdGVjaG5pcXVlCmJlY2F1c2Ugd2UgaGF2ZSBhIHNldCBvZiBsYWJlbGVkIHRyYWluaW5nIGRhdGEgdGhhdCB3ZSBjYW4gdXNlIHRvIGJ1aWxkCmEgY2xhc3NpZmllci4gTm93LCBob3dldmVyLCB3ZSBoYXZlIGFjY2VzcyB0byBkaWZmZXJlbnQgdGVjaG5pcXVlcwpiZWNhdXNlIHRoZSBzdHJ1Y3R1cmUgb2Ygb3VyIG91dGNvbWUgdmFyaWFibGUgaXMgY2F0ZWdvcmljYWwuCgojIyDwn5OdIFBvbGwgMTogQ2xhc3NpZmljYXRpb24gdnMuIFJlZ3Jlc3Npb24KCklkZW50aWZ5IHdoaWNoIG9mIHRoZSBmb2xsb3dpbmcgYXJlIGNsYXNzaWZpY2F0aW9uIHByb2JsZW1zIGluIG1hY2hpbmUKbGVhcm5pbmcuCgoxLiAgQW4gYWR2ZXJ0aXNlciBpcyBpbnRlcmVzdGVkIGluIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBhZ2UgYW5kIHRoZQogICAgbnVtYmVyIG9mIGhvdXJzIG9mIFlvdVR1YmUgY29uc3VtZWQuCgoyLiAgQSBtZWRpY2FsIHRlc3RpbmcgY29tcGFueSBjb25kdWN0cyBhIHByb2NlZHVyZSB0byBkZXRlcm1pbmUgd2hldGhlcgogICAgYSBwZXJzb24gaGFzIGEgY2FuY2VyIGRpYWdub3Npcy4KCjMuICBBIHJlc2VhcmNoZXIgaXMgaW50ZXJlc3RlZCBpbiB0aGUgZWZmZWN0IG9mIGFuIGVkdWNhdGlvbgogICAgaW50ZXJ2ZW50aW9uIG9uIHN0dWRlbnRzJyB0ZXN0IHNjb3Jlcy4KCjQuICBBIHNvZnR3YXJlIGVuZ2luZWVyIGlzIGRlc2lnbmluZyBhbiBhbGdvcml0aG0gdG8gZGV0ZWN0IHdoZXRoZXIgYW4KICAgIGVtYWlsIGlzIHNwYW0gb3Igbm90LgoKNS4gIEEgcG9saXRpY2FsIHNjaWVudGlzdCB3YW50cyB0byBjbGFzc2lmeSBUd2l0dGVyIHBvc3RzIGFzIHBvc2l0aXZlIG9yCiAgICBuZWdhdGl2ZS4KCgoKIyBUb2RheSdzIERhdGE6IFZvdGluZyBQYXR0ZXJucyBpbiB0aGUgMjAyMCBFbGVjdGlvbgoKTm93LCBsZXQncyBsb2FkIG91ciBwcmltYXJ5IGRhdGEgc2V0IGZvciB0b2RheSdzIHdvcmtzaG9wOiBgdm90ZTIwMjBgLgpPdXIgZ29hbCBpcyBnb2luZyB0byBiZSBwcmVkaWN0aW5nIHdoZXRoZXIgc29tZW9uZSB2b3RlZCBpbiB0aGUgMjAyMAplbGVjdGlvbiwgcGVyaGFwcyB0byB0YWlsb3IgZW5nYWdlbWVudCBzdHJhdGVnaWVzIHRvd2FyZCB0aG9zZSBsZWFzdApsaWtlbHkgdG8gdm90ZSBpbiBhbiB1cGNvbWluZyBlbGVjdGlvbi4gVGhlc2UgZGF0YSBhcmUgYmFzZWQgb24gdGhlCkN1cnJlbnQgUG9wdWxhdGlvbiBTdXJ2ZXkncyBbVm90ZXIKU3VwcGxlbWVudF0oaHR0cHM6Ly9jcHMuaXB1bXMub3JnL2Nwcy92b3Rlcl9zYW1wbGVfbm90ZXMuc2h0bWwpLgoKYGBge3J9CnZvdGUyMDIwIDwtIHJlYWQuY3N2KCIuLi9kYXRhL3ZvdGUyMDIwLmNzdiIscm93Lm5hbWVzID0gTlVMTCkKCiMgdmlzdWFsbHkgaW5zcGVjdCB0aGUgZGF0YSBmcmFtZSAKdmlldyh2b3RlMjAyMCkKYGBgCgojIEV4cGxvcmF0b3J5IEFuYWx5c2lzCgpSZWNhbGwgdGhhdCB0aGUgZmlyc3Qgc3RlcCBpbiBtYWNoaW5lIGxlYXJuaW5nIChhbmQgbW9zdCBhbmFseXNlcykgaXMgdG8KZXhwbG9yZSB0aGUgZGF0YSB3ZSdyZSB3b3JraW5nIHdpdGggdG8gZ2V0IGEgc2Vuc2Ugb2YgaXRzIHNoYXBlIGFuZAphbnRpY2lwYXRlIHByb2JsZW1zIHRoYXQgbWF5IGFyaXNlLgoKIyMg8J+liiBDaGFsbGVuZ2UgMTogRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcwoKUGVyZm9ybSBleHBsb3JhdG9yeSBhbmFseXNlcyBvbiB0aGUgYHZvdGUyMDIwYCBkYXRhIHNldCwga2VlcGluZyBpbiBtaW5kCnRoYXQgdG9kYXkncyBnb2FsIGlzIHRvIHByZWRpY3QgYHZvdGVkYC4gV2hhdCBkbyB5b3Ugbm90aWNlIGFib3V0IHRoZQpkYXRhPwoKYGBge3J9CgogIyMgWU9VUiBDT0RFIEhFUkUgCgpgYGAKCiMgVHJhaW4tVGVzdCBTcGxpdAoKUmVjYWxsIGZyb20gUGFydCAxIHRoYXQgYSBjcml0aWNhbCBmaXJzdCBzdGVwIGluIG1hY2hpbmUgbGVhcm5pbmcgaXMKcGFydGl0aW9uaW5nIG91ciBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cy4KCiMjIPCflJQgUXVlc3Rpb24gMTogVHJhaW4tVGVzdCBTcGxpdAoKQSByZXNlYXJjaGVyIHVzZWQgdGhlIGVudGlyZSBgdm90ZTIwMjBgIGRhdGEgc2V0IHRvIHRyYWluIGEgbW9kZWwgdGhhdApyZXN1bHRlZCBpbiBpbXByZXNzaXZlbHkgaGlnaCBhY2N1cmFjeSBkdXJpbmcgZXZhbHVhdGlvbi4gSG93ZXZlciwgdXBvbgpkZXBsb3lpbmcgdGhpcyBtb2RlbCB0byBwcmVkaWN0IHZvdGVyIHR1cm5vdXQgZm9yIGFuIHVwY29taW5nIGVsZWN0aW9uLAp0aGUgcHJlZGljdGlvbnMgc2lnbmlmaWNhbnRseSBkaXZlcmdlZCBmcm9tIGFjdHVhbCB0dXJub3V0LCB3aXRoIG1hbnkKZGlzY3JlcGFuY2llcyBpbiB3aG8gd2FzIHByZWRpY3RlZCB0byB2b3RlIHZlcnN1cyB3aG8gYWN0dWFsbHkgdm90ZWQuClRoZSBtb2RlbCdzIG5lYXItcGVyZmVjdCBwZXJmb3JtYW5jZSBpbiBkZXZlbG9wbWVudCBzdGFya2x5IGNvbnRyYXN0ZWQKaXRzIHBvb3IgcmVhbC13b3JsZCBwcmVkaWN0aW9uIG91dGNvbWVzLCBpbmRpY2F0aW5nIGEgcG90ZW50aWFsCm92ZXJzaWdodCBpbiB0aGUgbW9kZWwgdHJhaW5pbmcgYW5kIGV2YWx1YXRpb24gcHJvY2Vzcy4gV2h5IG1pZ2h0IHRoYXQKYmU/CgoqKkFuc3dlciAxKio6IAoKTm93IHRoYXQgd2UgaGF2ZSBhIGJldHRlciBzZW5zZSBvZiB0aGUgZGF0YSwgbGV0J3MgZ28gYWhlYWQgYW5kIHNwbGl0CmB2b3RlMjAyMGAgaW50byB0cmFpbmluZyBhbmQgdGVzdCBzZXRzOgoKYGBge3J9CiMgUGVyZm9ybSBzcGxpdHMKdm90ZTIwMjAkdm90ZWQgPC0gYXMuZmFjdG9yKHZvdGUyMDIwJHZvdGVkKQp2b3RlX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQodm90ZTIwMjAsIHByb3AgPSAwLjc1KQp2b3RlX3RyYWluIDwtIHRyYWluaW5nKHZvdGVfc3BsaXQpCnZvdGVfdGVzdCA8LSB0ZXN0aW5nKHZvdGVfc3BsaXQpCmBgYAoKIyBQcmUtUHJvY2Vzc2luZwoKSW4gb3VyIGV4YW1wbGUgZnJvbSBQYXJ0IDEsIHdlIHByZXBhcmVkIG91ciBkYXRhIGZvciBhbmFseXNpcyBieQpyZWNvZGluZyBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYW5kIG5vcm1hbGl6aW5nIG51bWVyaWMgb25lcyB1c2luZwpgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpYCBhbmQgYHN0ZXBfbm9ybWFsaXplKClgLApyZXNwZWN0aXZlbHkuCgpJbiB0aGF0IGV4YW1wbGUsIHdlIGFsc28gZHJvcHBlZCByb3dzIHRoYXQgaGFkIGFueSBtaXNzaW5nIHZhbHVlcyBhY3Jvc3MKdmFyaWFibGVzLiBMZXQncyB0cnkgYW5vdGhlciBleGFtcGxlLCBpbiB3aGljaCB3ZSBkb24ndCAqb21pdCogc2FtcGxlcwp0aGF0IGhhdmUgbWlzc2luZyB2YWx1ZXMsIGJ1dCBpbnN0ZWFkIHBlcmZvcm0gKmltcHV0YXRpb24qLCBpbiB3aGljaCB3ZQpyZXBsYWNlIHRob3NlIG1pc3NpbmcgdmFsdWVzIGFjY29yZGluZyB0byBjZXJ0YWluIGNyaXRlcmlhLiBUaGVyZSBhcmUKdmFyaW91cyBraW5kcyBvZiBpbXB1dGF0aW9uLCBpbmNsdWRpbmc6CgotICAgRm9yIGV4YW1wbGUsIHdoZW5ldmVyIHdlIGhhdmUgYSBtaXNzaW5nIHZhbHVlIGZvciB0aGUgYG9jY3VwYXRpb25gLAogICAgd2UgY2FuIHJlcGxhY2UgdGhhdCBtaXNzaW5nIHZhbHVlIHdpdGggdGhlIG1vc3QgY29tbW9uIG9jY3VwYXRpb24uCiAgICBUaGlzIGlzIGNhbGxlZCAqbW9kZSBpbXB1dGF0aW9uKi4KCi0gICBPciwgd2UgY291bGQgcmVwbGFjZSBhIG1pc3NpbmcgbnVtZXJpY2FsIHByZWRpY3RvciAoZS5nLiwgYWdlKSB1c2luZwogICAgdGhlIG1lZGlhbiBhY3Jvc3MgYWxsIHRoZSBzYW1wbGVzLiBUaGlzIGlzIGNhbGxlZCAqbWVkaWFuCiAgICBpbXB1dGF0aW9uKi4KClRoZXJlIGFyZSBvdGhlciB3YXlzIHRvIGltcHV0ZSwgYnV0IHRoZXNlIGFyZSBnb29kIHN0YXJ0aW5nIHBvaW50cy4gVGhlCndheSB0byBwZXJmb3JtIGltcHV0YXRpb24gaW4gYSBgcmVjaXBlYCBpcyB2aWEgdGhlIGBzdGVwX2ltcHV0ZV9gCmZ1bmN0aW9ucy4KCiMjIPCfpYogQ2hhbGxlbmdlIDI6IENyZWF0aW5nIGEgUmVjaXBlCgpVc2luZyB0aGUgc2FtZSBsb2dpYyBmcm9tIFBhcnQgMSwgY3JlYXRlIHlvdXIgb3duIHJlY2lwZSBjYWxsZWQKYHZvdGVyZWNpcGVgIHRoYXQgbGF5cyBvdXQgdGhlIHN0ZXBzIGZvciBwcmUtcHJvY2Vzc2luZyB0aGUgZGF0YS4KSW5zdGVhZCBvZiBkcm9wcGluZyByb3dzIHdpdGggbWlzc2luZyB2YWx1ZXMsIHVzZQpbc3RlcF9pbXB1dGVfbWVkaWFuXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3N0ZXBfaW1wdXRlX21lZGlhbi5odG1sKQp0byBpbXB1dGUgbnVtZXJpYyB2YXJpYWJsZXMgYW5kCltzdGVwX2ltcHV0ZV9tb2RlXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3N0ZXBfaW1wdXRlX21vZGUuaHRtbCkKdG8gaW1wdXRlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4KCmBgYHtyfQp2b3RlcmVjaXBlIDwtIAogIHJlY2lwZShfX19fXyB+IC4sIGRhdGEgPSBfX19fXykgJT4lCiAgc3RlcF9pbXB1dGVfbWVkaWFuKF9fX19fKSAlPiUKICBzdGVwX2ltcHV0ZV9tb2RlKF9fX19fKSAlPiUKICBzdGVwX2R1bW15KF9fX19fKSAKYGBgCgpUaGlzIHByZS1wcm9jZXNzaW5nIHdpbGwgYWxsb3cgdXMgdG8gdGFrZSBhZHZhbnRhZ2Ugb2Ygc2FtcGxlcyB3aXRoCm1pc3NpbmcgZGF0YSwgZXZlbiBpZiBpdCBjb21lcyBhdCBhIGxpdHRsZSBjb3N0IHRvIGFjY3VyYWN5LiBJbXB1dGF0aW9uCmlzIG9mdGVuIGEgbmVjZXNzYXJ5IHN0ZXAsIHNpbmNlIGl0J3MgY29tbW9uIHRvIGhhdmUgbWlzc2luZyBkYXRhLgoKIyMg8J+UlCBRdWVzdGlvbiAyOiBJbXB1dGF0aW9uCgpOb3RpY2UgdGhhdCB3ZSBoYXZlIG9ubHkgYXBwbGllZCBvdXIgcmVjaXBlIHRvIHRoZSB0cmFpbmluZyBkYXRhIHNvIGZhci4KRXhwbGFpbiB3aHkgaXQgaXMgaW1wb3J0YW50IHRvIHBlcmZvcm0gaW1wdXRhdGlvbiBzZXBhcmF0ZWx5IG9uIHRoZQp0cmFpbmluZyBhbmQgdGVzdCBzZXRzIHJhdGhlciB0aGFuIGltcHV0aW5nIG1pc3NpbmcgdmFsdWVzIGJlZm9yZQpzcGxpdHRpbmcgdGhlIGRhdGEgc2V0LiBDb25zaWRlciB3aHkgd2UgZG9uJ3Qgd2FudCB0aGUgZGF0YSB3ZSB0cmFpbiBvdXIKbW9kZWwgb24gdG8gYmUgZXhwb3NlZCB0byBvdXIgdGVzdCBkYXRhIHNldCBpbiB0aGUgZmlyc3QgcGxhY2UuCgoqKkFuc3dlciAyKio6IAoKIyBEZXZlbG9waW5nIGFuZCBFdmFsdWF0aW5nIE1vZGVscwoKTm93IHRoYXQgd2UndmUgcGVyZm9ybWVkIGV4cGxvcmF0b3J5IGFuYWx5c2VzLCBhIHRyYWluLXRlc3Qgc3BsaXQsIGFuZApwcmUtcHJvY2Vzc2luZywgbGV0J3MgZ28gYWhlYWQgYW5kIHRyYWluIG91ciBtb2RlbC4gV2Ugd2lsbCBjcmVhdGUgYQpjbGFzc2lmaWVyIChpLmUuLCBhIG1vZGVsIHRoYXQgcHJlZGljdHMgbWVtYmVyc2hpcCBpbiBhIGdyb3VwKSB3aXRoCmxvZ2lzdGljIHJlZ3Jlc3Npb24uCgojIyMjIEFsZ29yaXRobSAxOiBMb2dpc3RpYyBSZWdyZXNzaW9uCgpNYWNoaW5lIGxlYXJuaW5nIHByYWN0aXRpb25lcnMgb2Z0ZW4gcmVjb21tZW5kIGxvZ2lzdGljIHJlZ3Jlc3Npb24gYXMgYQpzdGFydGluZyBtb2RlbCB3aGVuIHByZWRpY3RpbmcgYSBiaW5hcnkgb3V0Y29tZSBvciBwcm9iYWJpbGl0eS4gRm9yCmV4YW1wbGUsIGlmIHdlIGFyZSBlc3RpbWF0aW5nIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBtb3J0YWxpdHkgYW5kCmluY29tZSwgd2UgY2FuIGVzdGltYXRlIHRoZSBwcm9iYWJpbGl0eSBvZiBtb3J0YWxpdHkgZ2l2ZW4gYSBjaGFuZ2UgaW4KaW5jb21lLgoKV2Ugd3JpdGUgdGhpcyBhcyAkUFtNfEldJCBhbmQgdGhlIHZhbHVlcyB3aWxsIHJhbmdlIGJldHdlZW4gMCBhbmQgMS4gV2UKY2FuIG1ha2UgYSBwcmVkaWN0aW9uIGZvciBhbnkgZ2l2ZW4gdmFsdWUgb2YgaW5jb21lIG9uIHRoZSBtb3J0YWxpdHkKb3V0Y29tZS4gTm9ybWFsbHksIHdlIGVzdGFibGlzaCBhIHRocmVzaG9sZCBmb3IgcHJlZGljdGlvbi4gRm9yIGV4YW1wbGUsCndlIG1pZ2h0IHByZWRpY3QgZGVhdGggKE0pIHdoZXJlICRQW018aV0gPiAwLjUkLgoKTG9naXN0aWMgcmVncmVzc2lvbiBpcyBhIGdlbmVyYWxpemVkIGxpbmVhciBtb2RlbCB3aGVyZSB3ZSBtb2RlbCB0aGUKcHJvYmFiaWxpdHkgZnVuY3Rpb24gdXNpbmcgdGhlIGxvZ2lzdGljIGZ1bmN0aW9uLCBhbiBzLXNoYXBlZCBjdXJ2ZQpzb21ldGltZXMgY2FsbGVkIGEgc2lnbW9pZCBmdW5jdGlvbi4gVGhlIGdlbmVyYWwgZnVuY3Rpb24gaXM6CiRwKFk9IDF8WCkkLiBJbiB0aGUgZXhhbXBsZSBiZWxvdywgdGhlIGdyZWVuIGRhdGEgcG9pbnRzIHdpbGwgYmUKY2xhc3NpZmllZCBhcyAwIGJlY2F1c2UgJHAkIFw8IDAuNTsgdGhlIG9yYW5nZSBwb2ludHMgd2lsbCBiZSBjbGFzc2lmaWVkCmFzIDEgYmVjYXVzZSAkcCQgXD4gMC41LgoKIVtMb2dpc3RpYyBSZWdyZXNzaW9uXSguLi9pbWFnZXMvTG9naXN0aWNfUmVncmVzc2lvbi5wbmcpCgpIZXJlJ3MgaG93IHRvIGZpdCBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtcyBpbiBgdGlkeW1vZGVsc2AgdXNpbmcgbG9naXN0aWMKcmVncmVzc2lvbi4KCkluIGB0aWR5bW9kZWxzYCwgY3JlYXRpbmcgYSBsb2dpc3RpYyByZWdyZXNzaW9uIGZvbGxvd3MgdGhlIGV4YWN0IHNhbWUKcHJvY2VkdXJlIGFzIGEgbGluZWFyIHJlZ3Jlc3Npb24uIFRoaXMgdGltZSwgaG93ZXZlciwgd2Ugd2lsbCB1c2UKYGxvZ2lzdGljX3JlZygpYCB0byBpbml0aWF0ZSB0aGUgZnVuY3Rpb24uIExldCdzIGNyZWF0ZSB0aGUgbW9kZWw6CgpgYGB7cn0KIyBDcmVhdGUgbW9kZWwKbG9naXN0aWNfbW9kZWwgPC0gbG9naXN0aWNfcmVnKG1vZGUgPSAiY2xhc3NpZmljYXRpb24iKQpgYGAKCk5vdywgbGV0J3MgZml0IHRoZSBtb2RlbCBvbiB0aGUgdHJhaW5pbmcgZGF0YS4gV2UgZmlyc3QgY3JlYXRlIGEKd29ya2Zsb3cgdGhhdCBsaXN0cyBpbnN0cnVjdGlvbnMgZm9yIHByZS1wcm9jZXNzaW5nIGFuZCB0aGUgbW9kZWwgd2UKd2FudCB0byB1c2UsIGFuZCB0aGVuIGFwcGx5IGl0IHRvIHRoZSB0cmFpbmluZyBkYXRhLgoKYGBge3J9CnZvdGVfd2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUKICBhZGRfcmVjaXBlKHZvdGVyZWNpcGUpICU+JQogIGFkZF9tb2RlbChsb2dpc3RpY19tb2RlbCkKCnZvdGVfZml0IDwtIGZpdCh2b3RlX3dmbG93LCB2b3RlX3RyYWluKQp2b3RlX2ZpdCAlPiUgdGlkeSgpCmBgYAoKRmluYWxseSwgbGV0J3MgdXNlIGF1Z21lbnQgZnJvbSB0aGUgYHlhcmRzdGlja2AgcGFja2FnZSB0byBvYnRhaW4gdGhlCnByZWRpY3Rpb25zLCBhbmQgdGFrZSBhIGxvb2sgYXQgdGhlbToKCmBgYHtyfQpsb2dpc3RpY19wcmVkaWN0aW9ucyA8LSBhdWdtZW50KHZvdGVfZml0LCBuZXdfZGF0YSA9IHZvdGVfdGVzdCkKbG9naXN0aWNfcHJlZGljdGlvbnNbLDE0OjE3XSAjIGxvb2sgYXQgbGFzdCA0IGNvbHVtbnMgCmBgYAoKIyMg8J+UlCBRdWVzdGlvbiAzOiBBbmFseXppbmcgT3V0cHV0CgpOb3RpY2UgdGhhdCBmb3VyIG5ldyBjb2x1bW5zIGhhdmUgYXBwZWFyZWQgaW4gb3VyIGRhdGEgc2V0LiBXaGF0IGRvCnRoZXNlIHZhbHVlcyBtZWFuPyBXaGF0IGFyZSB0aGV5IHRlbGxpbmcgdXM/CgoqKkFuc3dlciAzKio6IAoKVG8gZXZhbHVhdGUgdGhlIG1vZGVsLCB3ZSdsbCB1c2UgdGhlIGBhY2N1cmFjeWAgZnVuY3Rpb246CgpgYGB7cn0KYWNjdXJhY3kobG9naXN0aWNfcHJlZGljdGlvbnMsIHRydXRoID0gdm90ZWQsIGVzdGltYXRlID0gLnByZWRfY2xhc3MsIHR5cGU9ImNsYXNzIikKYGBgCgpXZSBwcmVkaWN0ZWQgdGhlIGxpa2VsaWhvb2QgdGhhdCBzb21lb25lIHZvdGVkIGluIHRoZSBsYXN0IGVsZWN0aW9uIHdpdGgKYW4gYWNjdXJhY3kgb2YgYWJvdXQgODElIC0gbm90IGJhZCEKCldlIGNhbiBhbHNvIGNyZWF0ZSB3aGF0J3MgY2FsbGVkIGEgJ2NvbmZ1c2lvbiBtYXRyaXgnIHRvIHNlZSBob3cgd2VsbApvdXIgbW9kZWwgZGlkIGF0IHByZWRpY3RpbmcgZWFjaCBjbGFzcy4gQSBjb25mdXNpb24gbWF0cml4IGlzIGhlbHBmdWwgaWYKeW91IGNhcmUgYWJvdXQgZmFsc2UgcG9zaXRpdmVzIChwcmVkaWN0aW5nIDEgd2hlbiB0aGUgdHJ1ZSB2YWx1ZSBpcyAwKQphbmQgZmFsc2UgbmVnYXRpdmVzIChwcmVkaWN0aW5nIDAgd2hlbiB0aGUgdHJ1ZSB2YWx1ZSBpcyAxKS4gSW4gdGhlCmNvbnRleHQgb2YgdGVzdGluZyBmb3IgQ292aWQtMTksIHdlIG1pZ2h0IHdhbnQgY2FyZSBtb3JlIGFib3V0IGZhaWxpbmcKdG8gZGlhZ25vc2UgYSBwZXJzb24gd2l0aCB0aGUgdmlydXMgKGkuZS4sIGEgZmFsc2UgbmVnYXRpdmUpIGJlY2F1c2UgdGhlCnN0YWtlcyBhcmUgaGlnaGVyLgoKYGBge3J9CmxvZ2lzdGljX3ByZWRpY3Rpb25zICU+JQogIGNvbmZfbWF0KHRydXRoID0gdm90ZWQsIGVzdGltYXRlID0gLnByZWRfY2xhc3MpCmBgYAoKU28gZmFyIHdlIGhhdmUgdHJhaW5lZCBhIHNpbXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLCB0aGVyZSBhcmUKbWFueSBvdGhlciBvcHRpb25zIGluIG91ciBtYWNoaW5lIGxlYXJuaW5nIGFyc2VuYWwgdG8gZ2V0IGEgYmV0dGVyCnByZWRpY3Rpb24uCgojIyMjIEh5cGVycGFyYW1ldGVyIFR1bmluZyBhbmQgUmVndWxhcml6YXRpb24KCk5vdGljZSB0aGF0IHVudGlsIG5vdywgd2UgaGF2ZSB1c2VkIHRoZSBkZWZhdWx0IHNldHRpbmdzIGZvciB0aGUgbGluZWFyCmFuZCBsb2dpc3RpYyByZWdyZXNzaW9ucyB3ZSBoYXZlIHJ1bi4gTG9va2luZyBhdCB0aGUKKGRvY3VtZW50YXRpb24pWzxodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvcGFyc25pcC92ZXJzaW9ucy8wLjAuMC45MDAxL3RvcGljcy9sb2dpc3RpY19yZWc+XQpmb3IgbG9naXN0aWMgcmVncmVzc2lvbiwgd2Ugc2VlIHRoYXQgdGhlcmUgYXJlIG1hbnkgYXJndW1lbnRzIHRoYXQgd2UKbGVmdCBibGFuayB3aGVuIHdlIGluaXRpYWxpemVkIG91ciBtb2RlbCBhYm92ZS4gVGhlc2UgYXJndW1lbnRzLCBhbHNvCmtub3duIGFzICdwYXJhbWV0ZXJzJywgY29ycmVzcG9uZCB0byBzdGF0aXN0aWNhbCBjaG9pY2VzIGFib3V0IGhvdyB0aGUKbW9kZWwgc2hvdWxkIG9wZXJhdGUgb3IgYmUgc3RydWN0dXJlZC4KClNvbWUgb2YgdGhlc2UgaHlwZXJwYXJhbWV0ZXJzIGluY2x1ZGUgJ2VuZ2luZScgYW5kICdwZW5hbHR5Jy4KCi0gICBFbmdpbmU6IERpZmZlcmVudCBlbmdpbmVzIGNhbiBpbXBsZW1lbnQgcmVncmVzc2lvbiB0aHJvdWdoIHZhcmlvdXMKICAgIGFsZ29yaXRobXMgb3IgY29tcHV0YXRpb25hbCBhcHByb2FjaGVzLiBXaGVuIHlvdSBzcGVjaWZ5IGFuIGVuZ2luZQogICAgZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaW4gUiwgeW91J3JlIGNob29zaW5nIHRoZSBwYXJ0aWN1bGFyIHNldCBvZgogICAgYWxnb3JpdGhtcyBhbmQgb3B0aW1pemF0aW9ucyB0aGF0IHdpbGwgYmUgdXNlZCB0byB0cmFpbiB5b3VyIG1vZGVsLgoKLSAgIFBlbmFsdHk6ICJwZW5hbHR5IiByZWZlcnMgdG8gYSByZWd1bGFyaXphdGlvbiB0ZWNobmlxdWUgdXNlZCB0bwogICAgcHJldmVudCBvdmVyZml0dGluZyBieSBkaXNjb3VyYWdpbmcgb3Zlcmx5IGNvbXBsZXggbW9kZWxzLiBJdCBkb2VzCiAgICB0aGlzIGJ5IGFkZGluZyBhIHBlbmFsdHkgdG8gdGhlIGxvc3MgZnVuY3Rpb24gZm9yIGxhcmdlCiAgICBjb2VmZmljaWVudHMuIENvbW1vbiBwZW5hbHRpZXMgYXJlIEwxIChMYXNzbyksIHdoaWNoIGNhbiBzaHJpbmsgc29tZQogICAgY29lZmZpY2llbnRzIHRvIHplcm8gKHRodXMgcGVyZm9ybWluZyBmZWF0dXJlIHNlbGVjdGlvbiksIGFuZCBMMgogICAgKFJpZGdlKSwgd2hpY2ggc2hyaW5rcyBhbGwgY29lZmZpY2llbnRzIHRvd2FyZCB6ZXJvIGJ1dCB0eXBpY2FsbHkKICAgIGRvZXNuJ3Qgc2V0IGFueSB0byBleGFjdGx5IHplcm8uIFRoZSBwZW5hbHR5IGhlbHBzIGluIGNyZWF0aW5nCiAgICBzaW1wbGVyLCBtb3JlIGdlbmVyYWxpemFibGUgbW9kZWxzIHRoYXQgcGVyZm9ybSBiZXR0ZXIgb24gdW5zZWVuCiAgICBkYXRhIGJ5IHByaW9yaXRpemluZyB0aGUgbW9zdCBpbmZsdWVudGlhbCBmZWF0dXJlcyBhbmQgcmVkdWNpbmcgdGhlCiAgICBtb2RlbCdzIHNlbnNpdGl2aXR5IHRvIHRoZSB0cmFpbmluZyBkYXRhJ3Mgbm9pc2UuCgpGb3IgZXhhbXBsZSwgd2UgY2FuIGFkZCBhICdwZW5hbHR5JyBvbiB0aGUgc2l6ZSBvZiB0aGUgY29lZmZpY2llbnRzIG9mIGEKbW9kZWwgYW5kIHJlZHVjZSB0aGUgbGlrZWxpaG9vZCBvZiBvdmVyZml0dGluZy4gTGFzc28sIHJpZGdlLCBhbmQKZWxhc3RpYyBuZXQgYXJlIGRpZmZlcmVudCB0eXBlcyBvZiBwZW5hbHRpZXMgdGhhdCBncmVhdGx5IHJlZHVjZSBvcgpzaHJpbmsgdG8gemVybyBjb2VmZmljaWVudHMgb24gdmFyaWFibGVzIHRoYXQgYXJlIHBpY2tpbmcgdXAgb24gYSBsb3Qgb2YKbm9pc2UuIFRoZSBicm9hZCB0ZWNobmlxdWUgb2YgcmVkdWNpbmcgb3ZlcmZpdHRpbmcgaXMgY2FsbGVkCidyZWd1bGFyaXphdGlvbi4nCgojIyDwn6WKIENoYWxsZW5nZSAzOiBDaGFuZ2luZyBIeXBlcnBhcmFtZXRlcnMKCldlIGhhdmUgcmVwcm9kdWNlZCB0aGUgb3JpZ2luYWwgY29kZSBmcm9tIGFib3ZlIHRoYXQgdHJhaW5lZCBhIGJhc2ljCmNsYXNzaWZpZXIgb24gb3VyIHZvdGluZyBkYXRhIHNldCB3aXRob3V0IGNoYW5naW5nIGFueSBvZiB0aGUKaHlwZXJwYXJhbWV0ZXJzLCBhcyB3ZWxsIGFzIHRoZSBjb2RlIHRoYXQgb2J0YWlucyBwcmVkaWN0aW9ucy4gUmUtcnVuCnRoaXMgY29kZSBzZXZlcmFsIHRpbWVzLCBidXQgZWFjaCB0aW1lIGNoYW5nZSB0aGUgaHlwZXJwYXJhbWV0ZXJzIGluIHRoZQptb2RlbCBzcGVjaWZpY2F0aW9uLiBGb3IgcGVuYWx0eSwgaW5jbHVkZSBhIG5vbi1uZWdhdGl2ZSBudW1iZXI7IGFuZCBmb3IKZW5naW5lLCBzZWxlY3QgZWl0aGVyICJnbG1uZXQiIG9yICJnbG0iLiBIb3cgZG9lcyB0aGUgYWNjdXJhY3kgY2hhbmdlPwoKYGBge3J9CmNoYWwzX21vZGVsIDwtIGxvZ2lzdGljX3JlZyhtb2RlID0gImNsYXNzaWZpY2F0aW9uIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmdpbmU9ImdsbW5ldCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGVuYWx0eT1fX19fKQoKY2hhbDNfd2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUKICBhZGRfcmVjaXBlKF9fX18pICU+JQogIGFkZF9tb2RlbChfX19fKQoKY2hhbDNfZml0IDwtIGZpdChjaGFsM193ZmxvdywgX19fXykKCmNoYWwzX3ByZWRpY3Rpb25zIDwtIGF1Z21lbnQoX19fXywgbmV3X2RhdGEgPSBfX19fKQoKYWNjdXJhY3koY2hhbDNfcHJlZGljdGlvbnMsIHRydXRoID0gX19fXywgZXN0aW1hdGUgPSAucHJlZF9jbGFzcywgdHlwZT0iY2xhc3MiKQpgYGAKCkl0J3MgbmljZSB0aGF0IHdlIGNhbiBkbyBkaWZmZXJlbnQgdHlwZXMgb2YgcmVndWxhcml6YXRpb24sIGJ1dCBob3cgZG8Kd2Uga25vdyB3aGF0IHZhbHVlIG9mIHRoZSBwZW5hbHR5IGNvZWZmaWNpZW50IHRvIHBpY2s/IEluIG1hY2hpbmUKbGVhcm5pbmcsIHRoaXMgdmFsdWUgLSB3aGljaCB3ZSBjaG9vc2UgYmVmb3JlIGZpdHRpbmcgdGhlIG1vZGVsIC0gaXMKa25vd24gYXMgYSBoeXBlcnBhcmFtZXRlci4gU2luY2UgaHlwZXJwYXJhbWV0ZXJzIGFyZSBjaG9zZW4gKmJlZm9yZSogd2UKZml0IHRoZSBtb2RlbCwgd2UgY2FuJ3QganVzdCBjaG9vc2UgdGhlbSBiYXNlZCBvZmYgdGhlIHRyYWluaW5nIGRhdGEuClNvLCBob3cgc2hvdWxkIHdlIGdvIGFib3V0IGNvbmR1Y3RpbmcgKipoeXBlcnBhcmFtZXRlciB0dW5pbmcqKjoKaWRlbnRpZnlpbmcgdGhlIGJlc3QgaHlwZXJwYXJhbWV0ZXIocykgdG8gdXNlPwoKTGV0J3MgdGhpbmsgYmFjayB0byBvdXIgb3JpZ2luYWwgZ29hbC4gV2Ugd2FudCBhIG1vZGVsIHRoYXQgZ2VuZXJhbGl6ZXMKdG8gdW5zZWVuIGRhdGEuIFNvLCBpZGVhbGx5LCB0aGUgY2hvaWNlIG9mIHRoZSBoeXBlcnBhcmFtZXRlciBzaG91bGQgYmUKc3VjaCB0aGF0IHRoZSBwZXJmb3JtYW5jZSBvbiB1bnNlZW4gZGF0YSBpcyB0aGUgYmVzdC4gV2UgY2FuJ3QgdXNlIHRoZQp0ZXN0IHNldCBmb3IgdGhpcywgYnV0IHdoYXQgaWYgd2UgaGFkIGFub3RoZXIgc2V0IG9mIGhlbGQtb3V0IGRhdGE/CgojIEh5cGVycGFyYW1ldGVyIHR1bmluZwoKQ3VlIGh5cGVycGFyYW1ldGVyIHR1bmluZyEgSHlwZXJwYXJhbWV0ZXIgdHVuaW5nIGlzIGNydWNpYWwgaW4gbWFjaGluZQpsZWFybmluZyBiZWNhdXNlIGl0IGRpcmVjdGx5IGltcGFjdHMgdGhlIHBlcmZvcm1hbmNlIGFuZCBlZmZlY3RpdmVuZXNzCm9mIG1vZGVscy4gQnkgZmluZS10dW5pbmcgaHlwZXJwYXJhbWV0ZXJzLCBwcmFjdGl0aW9uZXJzIGNhbiBvcHRpbWl6ZQptb2RlbHMgdG8gYWNoaWV2ZSBoaWdoZXIgYWNjdXJhY3ksIGJldHRlciBnZW5lcmFsaXplIHRvIHVuc2VlbiBkYXRhLCBhbmQKcHJldmVudCBpc3N1ZXMgbGlrZSBvdmVyZml0dGluZyBvciB1bmRlcmZpdHRpbmcuIFRoaXMgcHJvY2VzcyBhbGxvd3MgZm9yCnRoZSBjdXN0b21pemF0aW9uIG9mIG1vZGVscyB0byBzcGVjaWZpYyBkYXRhIHNldHMgYW5kIG9iamVjdGl2ZXMsCmVuYWJsaW5nIHRoZSBkaXNjb3Zlcnkgb2YgdGhlIGJlc3QgY29uZmlndXJhdGlvbiBmb3IgYSBnaXZlbiBwcm9ibGVtLgoKIyMgQ2hvb3NpbmcgSHlwZXJwYXJhbWV0ZXJzOiBWYWxpZGF0aW9uIFNldHMKClRoaXMgaXMgdGhlIGJhc2lzIGZvciBhICoqdmFsaWRhdGlvbiBzZXQqKi4gSWYgd2UgaGFkIGV4dHJhIGhlbGQtb3V0CmRhdGEgc2V0LCB3ZSBjb3VsZCB0cnkgYSBidW5jaCBvZiBoeXBlcnBhcmFtZXRlcnMgb24gdGhlIHRyYWluaW5nIHNldCwKYW5kIHNlZSB3aGljaCBvbmUgcmVzdWx0cyBpbiBhIG1vZGVsIHRoYXQgcGVyZm9ybXMgdGhlIGJlc3Qgb24gdGhlCnZhbGlkYXRpb24gc2V0LiBXZSB0aGVuIHdvdWxkIGNob29zZSB0aGF0IGh5cGVycGFyYW1ldGVyLCBhbmQgdXNlIGl0IHRvCnJlZml0IHRoZSBtb2RlbCBvbiBib3RoIHRoZSB0cmFpbmluZyBkYXRhIGFuZCB2YWxpZGF0aW9uIGRhdGEuIFdlIGNvdWxkCnRoZW4sIGZpbmFsbHksIGV2YWx1YXRlIG9uIHRoZSB0ZXN0IHNldC4KCiFbVmFsaWRhdGlvbiBzZXRdKC4uL2ltYWdlcy92YWxpZGF0aW9uLnBuZykKCiMgQ3Jvc3MgVmFsaWRhdGlvbgoKV2UganVzdCBmb3JtdWxhdGVkIHRoZSBwcm9jZXNzIG9mIGNob29zaW5nIGEgaHlwZXJwYXJhbWV0ZXIgd2l0aCBhCnNpbmdsZSB2YWxpZGF0aW9uIHNldC4gSG93ZXZlciwgdGhlcmUgYXJlIG1hbnkgd2F5cyB0byBwZXJmb3JtCnZhbGlkYXRpb24uIFRoZSBtb3N0IGNvbW1vbiB3YXkgaXMgKipjcm9zcy12YWxpZGF0aW9uKiouCkNyb3NzLXZhbGlkYXRpb24gaXMgbW90aXZhdGVkIGJ5IHRoZSBjb25jZXJuIHRoYXQgd2UgbWF5IG5vdCBjaG9vc2UgdGhlCmJlc3QgaHlwZXJwYXJhbWV0ZXIgaWYgd2UncmUgb25seSB2YWxpZGF0aW5nIG9uIGEgc21hbGwgZnJhY3Rpb24gb2YgdGhlCmRhdGEuIElmIHRoZSB2YWxpZGF0aW9uIHNhbXBsZSwganVzdCBieSBjaGFuY2UsIGNvbnRhaW5zIHNwZWNpZmljIGRhdGEKc2FtcGxlcywgd2UgbWF5IGJpYXMgb3VyIG1vZGVsIGluIGZhdm9yIG9mIHRob3NlIHNhbXBsZXMsIGFuZCBsaW1pdCBpdHMKZ2VuZXJhbGl6YWJpbGl0eS4KClNvLCBkdXJpbmcgY3Jvc3MtdmFsaWRhdGlvbiwgd2UgZWZmZWN0aXZlbHkgdmFsaWRhdGUgb24gdGhlICplbnRpcmUqCnRyYWluaW5nIHNldCwgYnkgYnJlYWtpbmcgaXQgdXAgaW50byBmb2xkcy4gSGVyZSdzIHRoZSBwcm9jZXNzOiBXZSBjYW4KdXNlIGEgcHJvY2VzcyBjYWxsZWQgY3Jvc3MtdmFsaWRhdGlvbiB0byBkbyBzZWxlY3QgdGhlIGJlc3QKCjEuICBQZXJmb3JtIGEgdHJhaW4tdGVzdCBzcGxpdCwgYXMgeW91IG5vcm1hbGx5IHdvdWxkLgoyLiAgQ2hvb3NlIGEgbnVtYmVyIG9mIGZvbGRzIC0gdGhlIG1vc3QgY29tbW9uIGlzICRLPTUkIC0gYW5kIHNwbGl0IHVwCiAgICB5b3VyIHRyYWluaW5nIGRhdGEgaW50byB0aG9zZSBlcXVhbGx5IHNpemVkICJmb2xkcyIuCjMuICBGb3IgZWFjaCBoeXBlcnBhcmFtZXRlciB5b3Ugd2FudCB0byB0dW5lLCBzcGVjaWZ5IHRoZSBwb3NzaWJsZQogICAgdmFsdWVzIGl0IGNvdWxkIHRha2UuIFRoZW4sIGZvciBlYWNoIGh5cGVycGFyYW1ldGVyOgogICAgMS4gIEZvciAqZWFjaCogKnZhbHVlKiBvZiB0aGF0IGh5cGVycGFyYW1ldGVyLCB3ZSdyZSBnb2luZyB0byBmaXQKICAgICAgICAkSyQgbW9kZWxzLiBMZXQncyBhc3N1bWUgJEs9NSQuIFRoZSBmaXJzdCBtb2RlbCB3aWxsIGJlIGZpdCBvbgogICAgICAgIEZvbGRzIDItNSwgYW5kIHZhbGlkYXRlZCBvbiBGb2xkIDEuIFRoZSBzZWNvbmQgbW9kZWwgd2lsbCBiZSBmaXQKICAgICAgICBvbiBGb2xkcyAxLCAzLTUsIGFuZCB2YWxpZGF0ZWQgb24gRm9sZCAyLiBUaGlzIHByb2Nlc3MgY29udGludWVzCiAgICAgICAgZm9yIGFsbCA1IHNwbGl0cy4KICAgIDIuICBUaGUgcGVyZm9ybWFuY2Ugb2YgZWFjaCB2YWx1ZSBvZiB0aGF0IGh5cGVycGFyYW1ldGVyIGlzCiAgICAgICAgc3VtbWFyaXplZCBieSB0aGUgYXZlcmFnZSBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIG9uIGFsbCA1CiAgICAgICAgaGVsZC1vdXQgZm9sZHMuIFdlIHRoZW4gY2hvb3NlIHRoZSBoeXBlcnBhcmFtZXRlciB2YWx1ZSB0aGF0IGhhZAogICAgICAgIHRoZSBiZXN0IGF2ZXJhZ2UgcGVyZm9ybWFuY2UuCjQuICBXZSBjYW4gdGhlbiByZWZpdCBhIG5ldyBtb2RlbCB0byB0aGUgZW50aXJlIHRyYWluaW5nIHNldCwgdXNpbmcgb3VyCiAgICBjaG9zZW4gaHlwZXJwYXJhbWV0ZXIocykuIFRoYXQncyBvdXIgZmluYWwgbW9kZWwgLSBldmFsdWF0ZSBpdCBvbgogICAgdGhlIHRlc3Qgc2V0IQoKIVtjcm9zcy12YWxpZGF0aW9uXShodHRwczovL3NjaWtpdC1sZWFybi5vcmcvc3RhYmxlL19pbWFnZXMvZ3JpZF9zZWFyY2hfY3Jvc3NfdmFsaWRhdGlvbi5wbmcpCgojIyBIeXBlcnBhcmFtZXRlciBUdW5pbmcgYW5kIENyb3NzIFZhbGlkYXRpb24gaW4gUHJhY3RpY2UKCldlIG5lZWQgdG8gZG8gdHdvIHRoaW5nczoKCjEuICBEZWNpZGUgdG8gcGVyZm9ybSBoeXBlcnBhcmFtZXRlciB0dW5pbmcgb24gdGhlIHBlbmFsdHkgdmFsdWUsIGFuZAoyLiAgRG8gc28gdXNpbmcgY3Jvc3MtdmFsaWRhdGlvbi4KClRoZSBgdGlkeW1vZGVsc2Agc3VpdGUgaGFzIHR3byBwYWNrYWdlcyB0byBoZWxwIHVzIHdpdGggdGhlc2Ugc3RlcHM6CmB0dW5lYCBhbmQgYHJzYW1wbGVgLgoKTGV0J3MgaWxsdXN0cmF0ZSBib3RoIHRoZXNlIHBhY2thZ2VzIGluIHRoZSBjbGFzc2lmaWNhdGlvbiBleGFtcGxlLiBXZQphbHJlYWR5IGhhdmUgYSByZWNpcGUgc2V0IHVwOgoKYGBge3J9CnZvdGVyZWNpcGUKYGBgCgpXaGVuIHNwZWNpZnlpbmcgdGhlIG1vZGVsLCBob3dldmVyLCB3ZSdyZSBnb2luZyB0byBkbyBzb21ldGhpbmcgc2xpZ2h0bHkKZGlmZmVyZW50OgoKYGBge3J9CnR1bmVkX2xvZ2lzdGljX21vZGVsIDwtIGxvZ2lzdGljX3JlZygKICBwZW5hbHR5ID0gdHVuZSgpLAogIGVuZ2luZSA9ICJnbG1uZXQiKQpgYGAKCldlIHBhc3NlZCBpbiBhIGZ1bmN0aW9uIGNhbGxlZCBgdHVuZSgpYC4gVGhpcyBzaWduYWxzIHRvIGB0aWR5bW9kZWxzYAp0aGF0IHdlJ2QgbGlrZSB0byB0dW5lIHRoaXMgaHlwZXJwYXJhbWV0ZXIuIEhvdyBkbyB3ZSBpbmRpY2F0ZSB3aGF0CnZhbHVlcyB3ZSBzaG91bGQgdGVzdCBkdXJpbmcgdHVuaW5nPyBXZSdyZSBnb2luZyB0byBrZWVwIHRoaW5ncyBzaW1wbGUKYW5kIGZvY3VzIG9uIHRoZSBtb3N0IGJhc2ljIGNob2ljZSBvZiB0dW5pbmc6IGEgZ3JpZCBzZWFyY2guIEluIHRoaXMKY2FzZSwgd2Ugc3BlY2lmeSBhIHJhbmdlIG9mIHZhbHVlcywgYW5kIHRoZW4gdGVzdCBldmVyeSBzaW5nbGUgb25lIGZvcgp0aGUgaHlwZXJwYXJhbWV0ZXIuIFdlIHVzZSB0aGUgYGdyaWRfcmVndWxhcmAgZnVuY3Rpb24gZm9yIHRoaXMKcHJvY2VkdXJlOgoKYGBge3J9CiMgQ3JlYXRlIGdyaWQgb2YgcGFyYW1ldGVycwpjdl9ncmlkIDwtIGdyaWRfcmVndWxhcigKICBwZW5hbHR5KHJhbmdlID0gYygtNSwgNSkpLCAjIHNlbGVjdCBwZW5hbHR5IHZhbHVlcyB3aXRoIC01IGFuZCA1IAogIGxldmVscz0xMCkgIyBwaWNrIDEwIHZhbHVlcyBiZXR3ZWVuIC01IGFuZCA1IAoKcHJpbnQoY3ZfZ3JpZCkKYGBgCgojIyDwn5SUIFF1ZXN0aW9uIDQ6IEFuYWx5emluZyBhIEdyaWQKCldlIGhhdmUgYSBoeXBlcnBhcmFtZXRlciBncmlkIHdpdGggMTAgcm93cy4gV2hhdCBkb2VzIGVhY2ggb2YgdGhlc2UKc2lnbmlmeT8KCioqU29sdXRpb24gNCoqOiAKCiMjIPCfjqwgKipEZW1vKio6IFBlcmZvcm1pbmcgSHlwZXJwYXJhbWV0ZXIgVHVuaW5nIHdpdGggQ3Jvc3MgVmFsaWRhdGlvbgoKTmV4dCwgd2UgbmVlZCB0byBzcGVjaWZ5IGhvdyB3ZSB3aWxsIHBlcmZvcm0gY3Jvc3MtdmFsaWRhdGlvbi4gRnJvbSB0aGUKYHJzYW1wbGVgIHBhY2thZ2UsIHdlIGNhbiB1c2UgdGhlIGZ1bmN0aW9uIGB2Zm9sZF9jdmAgdG8gY3JlYXRlIHRoZQp0cmFpbmluZyBmb2xkcy4gSW4gdGhpcyBjYXNlLCBgdmAgaXMgd2hhdCdzIHVzZWQgZm9yICJLIi4KCmBgYHtyfQp2b3RlX2ZvbGRzIDwtIHZmb2xkX2N2KHZvdGVfdHJhaW4sIHYgPSA1KQp2b3RlX2ZvbGRzCmBgYAoKV2UgaGF2ZSBhIHR1bmVkIG1vZGVsLCBhIGdyaWQgb2YgaHlwZXJwYXJhbWV0ZXJzLCBhbmQgYSBzZXQgb2YgZm9sZHMuIFdlCmNyZWF0ZSBvdXIgd29ya2Zsb3cgYXMgYmVmb3JlOiB3ZSBpbnB1dCB0aGUgcmVjaXBlIChpLmUuLCB0aGUgc2V0IG9mCnByZS1wcm9jZXNzaW5nIGluc3RydWN0aW9ucykgYW5kIHRoZSBtb2RlbCBzcGVjaWZpY2F0aW9uLiBCdXQsIHRvIHRyYWluCnRoZSB3b3JrZmxvdywgd2UgdXNlIHRoZSBgdHVuZV9ncmlkYCBmdW5jdGlvbi4gQWxsIHRoZSBwaWVjZXMgd2UndmUKY3JlYXRlZCBhcmUgcGFzc2VkIGludG8gdGhpcyBmdW5jdGlvbjoKCmBgYHtyfQojIENyZWF0ZSB3b3JrZmxvdwp2b3RlX3dmbG93IDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX3JlY2lwZSh2b3RlcmVjaXBlKSAlPiUKICBhZGRfbW9kZWwodHVuZWRfbG9naXN0aWNfbW9kZWwpCgojIFR1bmUgYW5kIGZpdCBtb2RlbHMgd2l0aCB0dW5lX2dyaWQoKQp2b3RlX2N2X2ZpdCA8LSB0dW5lX2dyaWQoCiAgIyBUaGUgd29ya2Zsb3cKICB2b3RlX3dmbG93LCAKICAjIFRoZSBmb2xkcyB3ZSBjcmVhdGVkCiAgcmVzYW1wbGVzID0gdm90ZV9mb2xkcywKICAjIFRoZSBncmlkIG9mIGh5cGVycGFyYW1ldGVycwogIGdyaWQgPSBjdl9ncmlkKQpgYGAKClRoZXJlIGFyZSBzb21lIG5pY2UgcGxvdHRpbmcgZnVuY3Rpb25zIHdlIGNhbiB1c2UgdG8gdmlzdWFsaXplIGhvdyB0aGUKcGVyZm9ybWFuY2UgdmFyaWVzIGFzIGEgZnVuY3Rpb24gb2YgdGhlIHJlZ3VsYXJpemF0aW9uLiBGb3IgZXhhbXBsZSwKY2hlY2sgb3V0IHRoZSBgYXV0b3Bsb3QoKWAgZnVuY3Rpb246CgpgYGB7cn0KYXV0b3Bsb3Qodm90ZV9jdl9maXQsIAogICAgICAgICBtZXRyaWM9ImFjY3VyYWN5IikgCmBgYAoKRWFjaCBvZiB0aGUgdGVuIHBvaW50cyBvbiB0aGlzIGdyYXBoIGNvcnJlc3BvbmRzIHRvIGEgc2V0IG9mCmh5cGVycGFyYW1ldGVycy4gV2UgY2FuIHNlZSB0aGF0IHRoZSB0aGlyZCBjb21iaW5hdGlvbiB5aWVsZHMgdGhlCmhpZ2hlc3QgYWNjdXJhY3kgb2YgYWJvdXQgODEuNSUuIFdoYXQgZG9lcyB0aGlzIHRlbGwgdXMgYWJvdXQgaG93IG11Y2gKcmVndWxhcml6YXRpb24gd2Ugc2hvdWxkIHVzZT8KCkluc3RlYWQgb2YgcGlja2luZyB0aGUgY29tYmluYXRpb24gb2YgaHlwZXJwYXJhbXRlcnMgdGhhdCB5aWVsZHMgdGhlCmJlc3QgYWNjdXJhY3kgYnkgZXllLCB3ZSBjYW4gYXV0b21hdGUgdGhpcyBwcm9jZWR1cmUuIFRoZSBgc2VsZWN0X2Jlc3RgCmZ1bmN0aW9uIHdpbGwgZG8gdGhpcyBmb3IgdXM6CgpgYGB7cn0KIyBTZWxlY3QgYmVzdCBtZXRyaWMgYWNjb3JkaW5nIHRvIGFjY3VyYWN5IAp2b3RlX2N2X2Jlc3QgPC0gc2VsZWN0X2Jlc3Qodm90ZV9jdl9maXQsIG1ldHJpYyA9ICJhY2N1cmFjeSIpCnZvdGVfY3ZfYmVzdApgYGAKCkNyb3NzLXZhbGlkYXRpb24gc2VsZWN0ZWQgYSBwZW5hbHR5IHZhbHVlIG9mIDAuMDAxNyBhcyB0aGUgYmVzdApoeXBlcnBhcmFtZXRlciBmb3Igb3VyIG1vZGVsLiBXaGF0IGRvIHdlIGRvIGF0IHRoaXMgcG9pbnQ/CgpSZWNhbGwgdGhhdCwgZHVyaW5nIGNyb3NzLXZhbGlkYXRpb24sIHdlIHNwbGl0IHVwIHRoZSBkYXRhIGFuZCBleGFtaW5lCnBlcmZvcm1hbmNlIGFjcm9zcyBtYW55IGZvbGRzLiBOb3cgdGhhdCB3ZSBrbm93IHdoYXQgaXMgbGlrZWx5IHRoZSBiZXN0CnBlbmFsdHksIHdlIGNhbiByZS10cmFpbiBvbiB0aGUgKmVudGlyZSogdHJhaW5pbmcgc2V0IHRvIHByb2R1Y2Ugb3VyCmZpbmFsIG1vZGVsLgoKV2UgZG8gdGhpcyB3aXRoIHRoZSBgZmluYWxpemVfd29ya2Zsb3dgIGZ1bmN0aW9uLgoKYGBge3J9CiMgR2V0IG91ciBmaW5hbCBtb2RlbCBhbmQgZmluYWxpemUgd29ya2Zsb3cKY3ZfZmluYWwgPC0gdm90ZV93ZmxvdyAlPiUKICBmaW5hbGl6ZV93b3JrZmxvdyhwYXJhbWV0ZXJzID0gdm90ZV9jdl9iZXN0KSAlPiUgIyB1c2UgdGhlIGJlc3QgcGFyYW1ldGVycyAKICBmaXQoZGF0YSA9IHZvdGVfdHJhaW4pCmN2X2ZpbmFsICU+JSB0aWR5KCkKYGBgCgpBbmQgbGFzdGx5LCB3ZSdsbCBleGFtaW5lIHRoZSBwZXJmb3JtYW5jZSBvbiB0aGUgdGVzdCBzZXQ6CgpgYGB7cn0KY3ZfcHJlZGljdGlvbnMgPC0gYXVnbWVudChjdl9maW5hbCwgbmV3X2RhdGEgPSB2b3RlX3Rlc3QpCgphY2N1cmFjeShjdl9wcmVkaWN0aW9ucywgdHJ1dGggPSB2b3RlZCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcywgdHlwZT0iY2xhc3MiKQpgYGAKCiMjIPCflJQgUXVlc3Rpb24gNTogQWNjdXJhY3kgb24gdGhlIFRlc3QgU2V0CgpOb3RpY2Ugb3VyIGZpbmFsIGFjY3VyYWN5IG9uIHRoZSB0ZXN0IHNldCBpcyBsb3dlciB0aGFuIG91ciBhY2N1cmFjeSBvbiB0aGUgdHJhaW5pbmcgc2V0LiBXaHkgbWlnaHQgdGhhdCBiZT8gCgoqKlNvbHV0aW9uIDUqKjogCgojIyBIeXBlcnBhcmFtZXRlciBUdW5pbmcgdnMuIERlZmF1bHQgQXJndW1lbnRzCgpJZiB3ZSBjb21wYXJlIHRoaXMgdG8gdGhlIG9yaWdpbmFsIG1vZGVsIHdlIGZpdCBlYXJsaWVyIHdoZXJlIHdlIG1hZGUgYQpndWVzcyBhdCB0aGUgYmVzdCB2YWx1ZSBmb3IgdGhlIHBlbmFsdHkgaHlwZXJwYXJhbWV0ZXIsIGl0IHJldmVhbHMgdGhhdAp3ZSB0aGF0IHdlIGNhbiBmaXQgYmV0dGVyIG1vZGVscyB2aWEgY3Jvc3MtdmFsaWRhdGlvbiB0aGFuIHdpdGgganVzdCBhCnNpbmdsZSB0cmFpbmluZyBzZXQuIFRoaXMgaXMgcmVmbGVjdGVkIGluIHRoZSBoaWdoZXIgYWNjdXJhY3kgY29tcGFyZWQKd2l0aCB0aGUgbW9kZWwgd2hlcmUgd2UgdXNlZCB0aGUgZGVmYXVsdCBhcmd1bWVudHMgb2YgYGxvZ2lzdGljX3JlZygpYC4KCiMgQ29uY2x1ZGluZyBSZW1hcmtzCgpDb25ncmF0dWxhdGlvbnMsIHlvdSd2ZSBtYWRlIGl0ISBXZSBjb3ZlcmVkIHRoZSBiYXNpY3Mgb2Ygc3VwZXJ2aXNlZAptYWNoaW5lIGxlYXJuaW5nIGluIGB0aWR5bW9kZWxzYCBpbiB0aGlzIHdvcmtzaG9wLiBIb3dldmVyLCB0aGVyZSdzIG11Y2gKbW9yZSB0byBleHBsb3JlLiBUaGUgYmVzdCB3YXkgdG8ga2VlcCBwdXNoaW5nIGZvcndhcmQgaXMgdG8gY2hvb3NlIGEKcHJvYmxlbSB0byBzdHVkeSwgYW5kIHJlZmVyIHRvIHRoZSBkb2N1bWVudGF0aW9uIHdoZW4geW91IG5lZWQgaGVscC4gVGhlCndlYnNpdGUgW0thZ2dsZV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS8pIGhhcyBtYW55IGdvb2QgZGF0YSBzY2llbmNlCnByb2JsZW1zIHRvIHdvcmsgb24gaWYgeW91IG5lZWQgaGVscCBjaG9vc2luZyBhIHRhc2shCg==